﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace CellularAutomaton.Core
{
	public class Grid
	{
		#region Private Fields

		private Dictionary<GridPoint, Cell> _cellsDictionary;
		private List<Cell> _computationList;
		private List<Cell> _updateList;
		private int _maxX;
		private int _maxY;

		#endregion


		#region Constructors

		public Grid(int maxX, int maxY)
		{
			InitializeFields(maxX, maxY);
			InitializeCells();
		}

		#endregion


		#region Public Methods

		public void ChangeCellState(int x, int y)
		{
			var cell = _cellsDictionary.Where(obj => obj.Key.X == x & obj.Key.Y == y).First().Value;
			cell.IsAlive = !cell.IsAlive;
		}

		public void NextStep()
		{
			_updateList = new List<Cell>(_computationList.Count);
			_updateList.AddRange(_computationList.Select(cell => cell));

			foreach (var cell in _computationList)
				cell.ComputeNewState();
			_computationList = new List<Cell>();

			foreach (var cell in _updateList)
				cell.UpdateState();
		}

		public DataTable GetStateDataTable()
		{
			var cellularAutomationStateDataTable = new DataTable("Cells States");

			var cellColl = from dict in this._cellsDictionary
						   select (ICellStateItem)dict.Value;

			foreach (var axisX in cellColl.OrderBy(obj => obj.X).Select(obj => obj.X).Distinct())
				cellularAutomationStateDataTable.Columns.Add(axisX.ToString(), Type.GetType("CellularAutomaton.Core.ICellStateItem"));

			foreach (var axisY in cellColl.OrderBy(obj => obj.Y).Select(obj => obj.Y).Distinct())
				cellularAutomationStateDataTable.Rows.Add(cellColl.Where(obj => obj.Y == axisY).OrderBy(obj => obj.X).ToArray());

			return cellularAutomationStateDataTable;
		}

		#endregion


		#region Private Methods

		private void InitializeCells()
		{
			// Создаем клетки и добавляем их в список клеток
			for (int y = 0; y < this._maxY; y++)
				for (int x = 0; x < this._maxX; x++)
				{
					var gridPoint = new GridPoint(x, y);
					this._cellsDictionary.Add(gridPoint, new Cell(gridPoint));
				}

			// Добавляем клеткам соседей. Ближаишие 8 клеток станут соседями, поверхность тора.
			// После добавления соседей инифиализируем клетку.
			foreach (var cell in this._cellsDictionary.Values)
			{
				//Debug.WriteLine("Current Cell: X={0}, Y={1}", cell.GridPosition.X, cell.GridPosition.Y);
				int neighbourX;
				int neighbourY;
				for (int dy = -1; dy <= 1; dy++)
				{
					if (cell.Y + dy < 0) neighbourY = cell.Y + dy + this._maxY;
					else if (cell.Y + dy >= this._maxY) neighbourY = cell.Y + dy - this._maxY;
					else neighbourY = cell.Y + dy;

					for (int dx = -1; dx <= 1; dx++)
					{
						if (dy == 0 & dx == 0) continue;
						if (cell.X + dx < 0) neighbourX = cell.X + dx + this._maxX;
						else if (cell.X + dx >= this._maxX) neighbourX = cell.X + dx - this._maxX;
						else neighbourX = cell.X + dx;

						//Debug.Write(String.Format("X={0}, Y={1};", neighbourX, neighbourY));
						cell.AddNewNeighbour(this._cellsDictionary.Where(obj => obj.Key.X == neighbourX & obj.Key.Y == neighbourY).First().Value);
					}
					//Debug.WriteLine("");
				}
				//Debug.WriteLine("");
				cell.InitializeCell(curCell =>
										{
											if (_computationList.Contains(curCell)) return;
											this._computationList.Add(curCell);
										});
			}
		}

		private void InitializeFields(int maxX, int maxY)
		{
			this._maxX = maxX;
			this._maxY = maxY;

			this._cellsDictionary = new Dictionary<GridPoint, Cell>(this._maxX * this._maxY);
			this._computationList = new List<Cell>(this._maxX * this._maxY);
			this._updateList = new List<Cell>(this._maxX * this._maxY);
		}

		#endregion
	}
}
